package esi.ws;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.util.EntityUtils;
import org.json.simple.parser.ContainerFactory;
import org.json.simple.parser.JSONParser;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

public class LocationResolver {

    final private String name;

    public LocationResolver(String movieName) {
        name = movieName;
    }

    public ArrayList<String> getMovieLocations() throws Exception {
        // Up to 10k chars are supported. Lets assume that the first 10k chars contain a parseable location name. We could do the fetching in a loop to cover the whole script but
        // in this case it isn't necessary.
        String screenplay = getScript(searchForScreenplay()).substring(0, 9999);
        return getLocations(locationsServiceRequest(screenplay));
    }

    private String searchForScreenplay()
            throws MalformedURLException, IOException {
        String url = "http://www.imsdb.com/feeds/fromtitle.php?title=" + URLEncoder.encode(name);
        String result = getURLResponse(url);
        return findScriptLink(result);
    }

    private String getScript(String link) throws MalformedURLException, IOException {
        Document doc = Jsoup.connect(link).get();
        Elements script = doc.select(".scrtext");
        return Jsoup.parse(script.get(0).toString()).text();
    }

    private String getURLResponse(String url) throws MalformedURLException, IOException {
        URLConnection connection = (new URL(url)).openConnection();

        BufferedReader in = new BufferedReader(new InputStreamReader(
                connection.getInputStream()));

        StringWriter writer = new StringWriter();
        IOUtils.copy(in, writer);
        String result = writer.toString();
        return result;
    }

    private String findScriptLink(String result) {
        Pattern pattern = Pattern
                .compile("http://www.imsdb.com/scripts/.*[^<]");
        Matcher matcher = pattern.matcher(result);
        matcher.find();
        return matcher.group().replace("</link>", "").trim();
    }

    private ArrayList<String> getLocations(String inputText)
            throws Exception {
        LinkedList<String> locList = null;

        JSONParser parser = new JSONParser();
        ContainerFactory containerFactory = new ContainerFactory() {
            @Override
            public List<String> creatArrayContainer() {
                return new LinkedList<String>();
            }

            @Override
            public Map createObjectContainer() {
                return new LinkedHashMap();
            }

        };

        Map json = (Map) parser.parse(inputText, containerFactory);
        locList = (LinkedList<String>) json.get("LOCATION");

        return new ArrayList<String>(locList);
    }

	static String locationsServiceRequest(String inputText) throws Exception {
		String responseJson = null;
		HttpParams params = new SyncBasicHttpParams();
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(params, "UTF-8");
		HttpProtocolParams.setUserAgent(params, "HttpComponents/1.1");
		HttpProtocolParams.setUseExpectContinue(params, true);

		HttpProcessor httpproc = new ImmutableHttpProcessor(
				new HttpRequestInterceptor[] { new RequestContent(),
						new RequestTargetHost(), new RequestConnControl(),
						new RequestUserAgent(), new RequestExpectContinue() });

		HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

		HttpContext context = new BasicHttpContext(null);

		HttpHost host = new HttpHost("text-processing.com", 80);

		DefaultHttpClientConnection conn = new DefaultHttpClientConnection();

		context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
		context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, host);

		try {

			HttpEntity[] requestBodies = { new StringEntity("text=" + inputText) };

			for (int i = 0; i < requestBodies.length; i++) {
				if (!conn.isOpen()) {
					Socket socket = new Socket(host.getHostName(),
							host.getPort());
					conn.bind(socket, params);
				}
				BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
						"POST", "/api/phrases/");
				request.setEntity(requestBodies[i]);

				request.setParams(params);
				httpexecutor.preProcess(request, httpproc, context);
				HttpResponse response = httpexecutor.execute(request, conn,
						context);
				response.setParams(params);
				httpexecutor.postProcess(response, httpproc, context);

				responseJson = EntityUtils.toString(response.getEntity());

				conn.close();

			}

		} finally {
			conn.close();
		}
		return responseJson;
	}

}
